#include <assert.h>
#include "sst_table.h"
#include "common.h"
#include <fstream>
#include <string>
#include <string.h>
#include <cstring>

namespace VirtualR {

void SSTable::dump_to_disk(std::string &sst_content) {
  FILE *fp = fopen(id_.GetTableId().c_str(), "w");
  if (fp == NULL) {
    std::cout << "[SSTable::dump_to_disk] no open" << std::endl;
    return;
  }
  auto size_d = 0;
  size_d = fwrite(&meta_size_, sizeof(uint64_t), 1, fp);
  if (size_d != 1) {
    std::cout << "[SSTable::dump_to_disk] write to disk error" << std::endl;
    assert(false);
  }
  size_d = fwrite(&total_count_, sizeof(uint64_t), 1, fp);
  if (size_d != 1) {
    std::cout << "[SSTable::dump_to_disk] write to disk error" << std::endl;
    assert(false);
  }

  uint64_t i = 0;
  uint32_t size = 0;
  for (; i < total_count_; i++) {
    size = keys_[i].size();
    size_d = fwrite(&size, sizeof(uint32_t), 1, fp);
    if (size_d != 1) {
      std::cout << "dump to disk error. size" << std::endl;
      assert(false);
    }

    char arr[keys_[i].size() + 1];
    strcpy(arr, keys_[i].c_str());
    // size_d = fwrite(arr, keys_[i].size(), 1, fp);
    size_d = fwrite(arr, sizeof(char), keys_[i].size(), fp);
    if (size_d != keys_[i].size()) {
      std::cout << "dump to disk error. keys_[i].c_str(). detail size: " << size_d << std::endl;
      assert(false);
    }
    size_d = fwrite(&offsets_[i], sizeof(uint64_t), 1, fp);
    if (size_d != 1) {
      std::cout << "dump to disk error. offsets_[i]" << std::endl;
      assert(false);
    }
  }
  size_d = fwrite(&offsets_[i], sizeof(uint64_t), 1, fp);
  if (size_d != 1) {
      std::cout << "dump to disk error. offsets_[i]" << std::endl;
      assert(false);
  }
  
  char arr[sst_content.size() + 1];
  strcpy(arr, sst_content.c_str());
  size_d = fwrite(arr, sizeof(char), sst_content.size(), fp);
  if (size_d != sst_content.size()) {
      std::cout << "dump to disk error. sst_content" << std::endl;
      assert(false);
  }
  fclose(fp);
}

SSTable::SSTable(SSTableID &id) {
  id_ = id;
  FILE *file = fopen(id_.GetTableId().c_str(), "r");
  if (file == NULL) {
    std::cout << "open error" << std::endl;
    assert(false);
  }
  uint32_t size_d = 0;
  size_d = fread(&meta_size_, sizeof(uint64_t), 1, file);
  if (size_d != 1) {
    std::cout << "SSTable read meta_size error." << std::endl;
    assert(false);
  }
  size_d = fread(&total_count_, sizeof(uint64_t), 1, file);
  if (size_d != 1) {
    std::cout << "SSTable read meta_size error." << std::endl;
    assert(false);
  }
  uint32_t size = 0;
  uint64_t offset = 0;
  for (uint64_t i = 0; i < total_count_; i++) {
    size_d = fread(&size, sizeof(uint32_t), 1, file);
    if (size_d != 1) {
      std::cout << "size uint32_t. error " << i << std::endl;
      assert(false);
    }
    char str[size + 1];
    size_d = fread(str, sizeof(char), size, file);
    str[size] = '\0';
    std::string key(str);
    if (size_d != size) {
      std::cout << "str " << size << ".error " << i << " str: " << key << std::endl;
      assert(false);
    }
    size_d = fread(&offset, sizeof(uint64_t), 1, file);
    // std::cout << key << std::endl;
    if (size_d != 1) {
      std::cout << "offset uint64_t. error " << i << std::endl;
      assert(false);
    }
    offsets_.push_back(offset);
    keys_.push_back(key);
  }
  size_d = fread(&offset, sizeof(uint64_t), 1, file);
  if (size_d != 1) {
    std::cout << "error end" << std::endl;
    assert(false);
  }
  offsets_.push_back(offset);
  fclose(file);
}

SSTable::SSTable(std::vector<Entry_t> &entries, uint64_t &pos, SSTableID &id) {
  id_ = id;
  meta_size_ = 0;
  // meta_size
  meta_size_ += sizeof(uint64_t);
  // total_count_ size
  meta_size_ += sizeof(uint64_t);
  std::string values;
  values.reserve(2 * 1024 * 1024);
  uint64_t entry_size = entries.size();
  uint64_t offset = 0;
  while (entry_size > pos && values.size() + entries[pos].value.size() <= 2 * 1024 * 1024) {
    std::string key = entries[pos].key;
    std::string value = entries[pos].value;
    keys_.push_back(key);
    offsets_.push_back(offset);
    offset += value.size();
    values += value;
    meta_size_ += sizeof(uint32_t);
    meta_size_ += key.size();
    meta_size_ += sizeof(uint64_t);
    pos++;
    total_count_++;
  }
  offsets_.push_back(offset);
  meta_size_ += sizeof(uint64_t);
  dump_to_disk(values);
}

SSTable::SSTable(SkipList *mem, SSTableID id) {
  if (mem == nullptr) {
    std::cout << "mem is nullptr" << std::endl;
    assert(false);
  }
  meta_size_ = 0;
  // meta_size
  meta_size_ += sizeof(uint64_t);
  // total_count_ size
  meta_size_ += sizeof(uint64_t);
  id_ = id;
  // k-v 数量
  uint64_t size = mem->get_entry_size();
  uint64_t offset = 0;
  std::string values;
  values.reserve(2 * 1024 * 1024); // 一个sst value占2MB
  
  SkipList::Iterator *it = mem->iterator();
  // mem小于
  while (it->valid()) {
    it->next();
    if (!it->valid()) {
      break;
    }
    total_count_++;
    std::string key = it->key();
    std::string value = it->value();
    // 4字节长度 + 实际内容
    keys_.push_back(key);
    offsets_.push_back(offset);
    offset += value.size();
    values += value;
    meta_size_ += sizeof(uint32_t);
    meta_size_ += key.size();
    meta_size_ += sizeof(uint64_t);
  }
  offsets_.push_back(offset);
  meta_size_ += sizeof(uint64_t);
  dump_to_disk(values);
}

std::vector<Entry_t> SSTable::load() {
  std::vector<Entry_t> entries;
  auto size_d = 0;
  FILE *fp = fopen(id_.GetTableId().c_str(), "r");
  if (fp == NULL) {
    std::cout << "[SSTable::load] no open" << std::endl;
    return entries;
  }
  fseek(fp, meta_size_, SEEK_SET);
  for (uint64_t i = 0; i < total_count_; i++) {
    std::string key = keys_[i];
    
    char str[offsets_[i + 1] - offsets_[i] + 1];

    size_d = fread(str, sizeof(char), offsets_[i + 1] - offsets_[i], fp);
    if (size_d != offsets_[i + 1] - offsets_[i]) {
      std::cout << "key: " << key << std::endl;
      std::cout << "SSTable::load error. fread err. real read: " << size_d << " want: " << offsets_[i + 1] - offsets_[i] << std::endl;
      assert(false);
    }
    fflush(fp);
    str[offsets_[i + 1] - offsets_[i]] = '\0';
    std::string value(str);
    if (!value.empty()) {
      Entry_t tmp_entry(key, value);
      entries.push_back(tmp_entry);
    }
  }
  fclose(fp);
  return entries;
}

std::string SSTable::lower() {
  return keys_[0];
}

std::string SSTable::higher() {
  return keys_[total_count_ - 1];
}

uint64_t SSTable::no() {
  return id_.GetNo();
}

uint64_t SSTable::disk_size() {
  // offsets_.back() the last value offset. means values total size
  return meta_size_ + offsets_.back();
}

int32_t SSTable::remove_file() {
  return remove(id_.GetTableId().c_str());
}

std::string SSTable::get_value(uint32_t pos) {
  FILE *fp = fopen(id_.GetTableId().c_str(), "r");
  if (fp == NULL) {
    std::cout << "[SSTable::get_value] no open: " << id_.GetTableId().c_str() << std::endl;
    return "";
  }

  fseek(fp, meta_size_ + offsets_[pos], SEEK_SET);
  char str[offsets_[pos + 1] - offsets_[pos] + 1];
  fread(str, offsets_[pos + 1] - offsets_[pos], 1, fp);
  str[offsets_[pos + 1] - offsets_[pos]] = '\0';
  std::string value(str);
  fclose(fp);
  return value;
}

std::pair<bool, std::string> SSTable::search(std::string key) {
  for (uint32_t i = 0; i < total_count_; i++) {
    if (keys_[i] == key) {
      return {true, get_value(i)};
    }
    if (keys_[i] > key) {
      return {false, ""};
    }
  }
  return {false, ""};

  uint32_t low = 0, high = total_count_;
  while (high > low + 2) {
    uint32_t mid = low + (high - low) / 2;
    if (keys_[mid] < key) {
      low = mid;
    } else if (keys_[mid] > key) {
      high = mid;
    } else {
      return {true, get_value(mid)};
    }
  }

  if (keys_[low] == key) {
    return {true, get_value(low)};
  } else if (keys_[high - 1] == key) {
    return {true, get_value(high - 1)};
  } else {
    return {false, ""};
  }
}

} // namespace VirtualR